﻿using System.Threading;
using DM4;
using sscore;
using System;
using System.Collections.Generic;
using sscore.Utils;
using System.Text.RegularExpressions;
using System.IO;
using sscore.Requests;

namespace IN
{
    public class IN : ModuleBase
    {
        private readonly string _searchUrl;
        private readonly string _offenderLink;
        private const string StateName = "IN";
        private ResultFile _imagesResultFile;

        private const string CountyId = "54663";
        private static string _url = "";
        private static string _prevurl = "";

        public IN()
        {
            //initialize main system constants
            ModuleCode = "SO_IN";
            Description = "IN Sex Offenders";
            Version = "1.0";

            //for creating sessions
            StartParam = 1;
            StopParam = 500;

            //initialize help 
            AddToHelp(DM4Consts.StartPosition, String.Format("{0}...{1}", StartParam, StopParam));
            AddToHelp(DM4Consts.EndPositon, String.Format("{0}...{1}", StartParam, StopParam));

            AddToHelp(DM4Consts.IsDownload, "true|false");
            AddToHelp(DM4Consts.IsDownloadImg, "true|false");
            AddToHelp(DM4Consts.InsertToDb, "false|true", "(Expected: servername, dbname)");
            AddToHelp(DM4Consts.ServerName, "");
            AddToHelp(DM4Consts.DbName, "");
            AddToHelp(DM4Consts.IsClearDb, "false|true");
            AddToHelp(DM4Consts.TestParam, "false|true");

            Rpm.Codepage = EncodingType.Iso88591;
            RootUrl = "http://www.icrimewatch.net/";
            _searchUrl = String.Format("{0}results.php?AgencyID={1}&SubmitAllSearch=1&page={{0}}", RootUrl, CountyId);
            _offenderLink = String.Format("{0}offenderdetails.php?OfndrID={{0}}&AgencyID={1}", RootUrl, CountyId);
        }

        private void SetParametersDefaults()
        {
            Params.CreateBooleanParam(DM4Consts.IsDownload, true);
            Params.CreateBooleanParam(DM4Consts.IsDownloadImg, true);
            Params.CreateBooleanParam(DM4Consts.InsertToDb, false);
            Params.CreateBooleanParam(DM4Consts.IsClearDb, false);
            Params.CreateStringParam(DM4Consts.DbName, "");
            Params.CreateStringParam(DM4Consts.ServerName, "");
            Params.CreateIntegerParam(DM4Consts.StartPosition, StartParam);
            Params.CreateIntegerParam(DM4Consts.LastPosition, Params.GetIntegerParam(DmConsts.StartPosition));
            Params.CreateIntegerParam(DM4Consts.EndPositon, StopParam);
            Params.CreateIntegerParam(DM4Consts.LastRecord, 0);
        }

        public override OperationResult Init(IDM4Core iCore, DmTaskParams param)
        {
            //use basic initialization sequence
            base.Init(iCore, param);

            SetParametersDefaults();
            return new OperationResult { Result = DM4Consts.Ok };
        }

        public override OperationResult Start()
        {
            DoWork();
            return new OperationResult { Result = DM4Consts.Ok };
        }

        public override OperationResult DoWork()
        {
            OperationResult or = new OperationResult { Result = DM4Consts.ErrorUnknown };

            if (!Ct.IsCancellationRequested)
            {
                WriteLog("Start parameters:", MessageType.Warning);
                foreach (KeyValuePair<string, string> kvp in Params.GetDictionary()) //get all existing parameters
                    WriteLog(String.Format("Key = {0}, Value = {1}", kvp.Key, kvp.Value), MessageType.Warning);

                if (Params.GetBooleanParam(DM4Consts.IsDownload))
                    Load();

                if (Params.GetBooleanParam(DM4Consts.InsertToDb))
                    if ((Params.GetStringParam(DM4Consts.DbName) == "") || (Params.GetStringParam(DM4Consts.ServerName) == ""))
                    {
                        or.Message = String.Format("{0} or {1} is empty", DM4Consts.DbName, DM4Consts.ServerName);
                        ResultCode = DM4Consts.SessionResultCodeError;
                        ResultMessage = or.Message;
                        UpdateStatus(DmConsts.Error);
                        WriteErrorLog(or.Message); //writting to error and session log   

                        return or;
                    }
                    else
                    {
                        //insert offenders to db 
                        UpdateStatus(DM4Consts.SessionInsertToDb);

                        Offenders.OffInsertToDatabase(StateName, Params.GetStringParam(DmConsts.DbName),
                             Params.GetStringParam(DmConsts.ServerName), ResultDir, Params.GetBooleanParam(DmConsts.IsClearDb),
                             WriteLog);

                    }

                //check if need abort task
                if (Ct.IsCancellationRequested)
                {
                    ResultCode = DM4Consts.SessionResultTerminateByUser;
                    ResultMessage = "Session terminated by user";
                    UpdateStatus(DM4Consts.SessionWait);
                    WriteLog("Session aborted by user", MessageType.Cool);
                }
                else
                {
                    UpdateProgress(1);
                    ResultCode = DM4Consts.SessionResultCodeOk;
                    ResultMessage = String.Format("Session complete, errors : {0}, records : {1}", TaskParams.ErrorCount, TaskParams.RecordCount);
                    UpdateStatus(DM4Consts.SessionComplete);
                    WriteLog("------------Finish----------------", MessageType.Cool);
                }
            }
            else
            {
                ResultCode = DM4Consts.SessionResultTerminateByUser;
                ResultMessage = "Session terminated by user";
                UpdateStatus(DM4Consts.SessionWait);
                WriteLog("Session aborted by user", MessageType.Cool);
            }


            return or;
        }

        private void Load()
        {
            string resultFileName = String.Format("{0}\\{1}.{2}", ResultDir, TaskParams.SessionId, DmConsts.ResultFilesExtension);
            bool workFlag = true;

            ResultFile = new ResultFile(resultFileName, OffendersResult.GetHeader());
            _imagesResultFile = new ResultFile(String.Format("{0}\\{1}_images.{2}", ResultDir, TaskParams.SessionId, DM4Consts.ResultFilesExtension), "FileName|FileUrl");
            try
            {
                while (workFlag)
                {
                    //рабочий цикл
                    string error;

                    try
                    {
                        error = "";  //Required!!  /*if (!String.IsNullOrEmpty(error)) --> restart thread; else --> download finished*/

                        WriteLog("Start download...");
                        Searching();
                    }
                    catch (Exception e)
                    {
                        error = e.Message;
                    }

                    if (!String.IsNullOrEmpty(error))
                    {
                        Thread.Sleep(30000);
                        WriteErrorLog(error);
                        WriteLog("Try continue work...", MessageType.Warning);
                    }
                    else
                    {
                        workFlag = false;  //все нормально откачало, заканчиваем работу
                    }
                }
            }
            finally
            {
                ResultFile.Close();
                _imagesResultFile.Close();
            }
        }

        private void GetCookies()
        {
            _url = "http://www.communitynotification.com/cap_office_disclaimer.php?office=" + CountyId;
            string html = GetRequest.ReturnCookies(_url, Rpm);
            Rpm.Cookies = Rpm.Cookies.Replace("path=/,", "").Replace("path=/", "");

            WriteLog("Agree to the above terms & conditions.");
            html = PostRedirectRequest.RefererWithCookiesReturnCookies(_url, _url, "agree=1&continue=Continue", Rpm);

            _prevurl = _url;
            _url = String.Format("{0}index.php?AgencyID={1}&disc={1}", RootUrl, CountyId);
            html = GetRequest.RefererWithCookiesReturnCookies(_url, _prevurl, Rpm);
            Rpm.Cookies = Rpm.Cookies.Replace("path=/,", "").Replace("path=/", "");

            _prevurl = Params.GetIntegerParam(DmConsts.LastPosition) == 1
                ? _url
                : string.Format(_searchUrl, Params.GetIntegerParam(DmConsts.LastPosition) - 1);

            _url = string.Format(_searchUrl, Params.GetIntegerParam(DmConsts.LastPosition));
        }

        private void Searching()
        {
            if (Test)
            {
                Params.SetIntegerParam(DmConsts.LastPosition, Params.GetIntegerParam(DmConsts.StartPosition));
                Params.SetIntegerParam(DmConsts.EndPositon, Params.GetIntegerParam(DmConsts.StartPosition));
            }

            GetCookies();
            bool isNextPageExists;
            do
            {
                //check if need abort task
                if (Ct.IsCancellationRequested)
                    break;

                UpdateProgress();

                WriteLog(String.Format("navigate to page {0} ", Params.GetIntegerParam(DmConsts.LastPosition)));
                string html = GetRequest.RefererWithCookies(_url, _prevurl, Rpm);

                if (Params.GetIntegerParam(DmConsts.LastPosition) == StopParam)
                    Params.SetIntegerParam(DmConsts.LastPosition,
                        Convert.ToInt32(RegExpr.Parse(html, @"(?si:Found\s*([\d]+)\s*offenders)")) / 30 + 1);


                ParseResultPage(html);

                isNextPageExists = false;
                if (RegExpr.IsMatch(html, "button_next.jpg"))
                {
                    Params.SetIntegerParam(DmConsts.LastPosition, Params.GetIntegerParam(DmConsts.LastPosition) + 1);
                    Params.SetIntegerParam(DmConsts.LastRecord, 0);

                    _prevurl = _url;
                    _url = string.Format(_searchUrl, Params.GetIntegerParam(DmConsts.LastPosition));
                    isNextPageExists = true;
                }
            } while (isNextPageExists);
        }

        private void ParseResultPage(string html)
        {
            html = HtmlUtils.KillSpace(HtmlUtils.ReplaceSpecSymbols(html.Replace("\r\n", " ").Replace("\t", " ").Replace("\r", " ").Replace("\n", " ")));

            if (!RegExpr.IsMatch(html, @"(?si:Offender Search: Results)"))
                throw new Exception(String.Format("Result page: {0}{1}{2}", DmConsts.ExcNeedRestart, Environment.NewLine, html));

            MatchCollection mc = RegExpr.ParseMatchCollection(
               RegExpr.Parse(html, @"(?si:>\s*View Details\s*</span>(.*?)</table>)"),
               @"(?si:<tr>\s*<td[^<>]*?>(.*?)OfndrID\x3D([\d]+)&AgencyID=([\d]+)""\s*>\s*<img src=""http://www.sheriffalerts.com/images/cap_view.gif.*?</tr>)");

            WriteLog(String.Format("found [{0}] matches", mc.Count));
            if (mc.Count == 0)
                throw new Exception(DmConsts.ExcNeedRestart + Environment.NewLine + html);

            int iOffender = 0;
            foreach (Match matchLoc in mc)
            {
                if (Ct.IsCancellationRequested)
                    break;

                if (iOffender >= Params.GetIntegerParam(DmConsts.LastRecord))
                {
                    string lookupKey = matchLoc.Groups[2].Value;
                    string imageUrl = RegExpr.Parse(matchLoc.Groups[1].Value, @"(?si:<img src=""([^""]+)"")");
                    string offenderHtml = GetRedirectRequest.WithCookies(string.Format(_offenderLink, lookupKey), Rpm);


                    WriteLog(String.Format("ParseOffender [{0}][{1}] ", Params.GetIntegerParam(DmConsts.LastRecord), lookupKey));

                    if (!RegExpr.IsMatch(offenderHtml, @"(?si:Offender Search: Offender Details)"))
                        throw new Exception("Offenders page:" + DmConsts.ExcNeedRestart);
                    ParseOffender(offenderHtml, lookupKey, imageUrl);

                    UpdateRecordCount(1);
                    Params.SetIntegerParam(DmConsts.LastRecord, Params.GetIntegerParam(DmConsts.LastRecord) + 1);
                }
                iOffender++;
            }

            Params.SetIntegerParam(DmConsts.LastRecord, 0);
        }

        private void ParseOffender(string offenderHtml, string lookupKey, string imageUrl)
        {
            offenderHtml = HtmlUtils.KillSpace(HtmlUtils.ReplaceSpecSymbols(offenderHtml.Replace("\r\n", " ").Replace("\t", " ").Replace("\r", " ").Replace("\n", " ")));
            using (OffendersResult or = new OffendersResult())
            {
                or.Source_State = StateName;
                or.offenderLink = string.Format(_offenderLink, lookupKey);
                or.Source_id = lookupKey;

                if (Params.GetBooleanParam(DmConsts.IsDownloadImg) && !string.IsNullOrEmpty(imageUrl))
                {
                    or.Image_ID = Guid.NewGuid() + ".jpg";
                    //imageUrl is always temporary link
                    try
                    {
                        Offenders.DownloadImageWithExceptions(_imagesResultFile, String.Format("{0}\\{1}\\{2}", ResultDir, DmConsts.PhotosDir, or.Image_ID),
                            imageUrl, TaskParams.SessionId);
                    }
                    catch (Exception ex)
                    {
                        //если произошла загрузка битой картинки
                        if (ex.Message == "Parameter is not valid." && ex.StackTrace.StartsWith("   at System.Drawing.Bitmap..ctor(String filename)"))
                        {
                            or.Image_ID = "";
                            or.ImageLink = "";
                        }
                        else
                            throw new Exception("Error occured while downloading image from offenders page: " + or.offenderLink, ex);
                    }
                }
                if (or.Image_ID != "" && !File.Exists(String.Format("{0}\\{1}\\{2}", ResultDir, DmConsts.PhotosDir, or.Image_ID)))
                    throw new Exception(DmConsts.ExcImgNotFound);

                or.Status = offenderHtml.Contains("Non-compliant") ? "Non-compliant" : "";

                offenderHtml = HtmlUtils.KillSpace(RegExpUtils.DeleteTag(offenderHtml));

                or.fullname = RegExpr.Parse(offenderHtml, @"(?si:Name\s*:\s*</td>\s*<td>(.*?)</td>)").Trim();

                MatchCollection mc = RegExpr.ParseMatchCollection(
                    RegExpr.Parse(offenderHtml, @"(?si:Aliases:</strong>\s*</td>(.*?)<strong>)"),
                    "(?si:<td>(.*?)</td>)");

                foreach (Match matchLoc in mc)
                    if (RegExpUtils.DeleteTag(matchLoc.Groups[1].Value.Trim()) != "")
                        or.AKA += String.Format("{0};", RegExpUtils.DeleteTag(matchLoc.Groups[1].Value.Trim()));

                if (or.AKA.Contains("<strong>"))
                    or.AKA = RegExpr.Parse(or.AKA, @"(?si:^(.*?)<strong>)").Trim();

                or.Age = RegExpr.Parse(offenderHtml, @"(?si:Age\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();
                if (or.Age.Contains("(DOB:"))
                {
                    //or.Birthdate = RegExpr.Parse(or.Age, @"(?si:^.*?\x28DOB:([^\x28\x29]*?)\x29)").Trim();
                    or.Age = RegExpr.Parse(or.Age, @"(?si:^(.*?)\x28DOB:)").Trim();
                }

                or.Ethnicity = RegExpr.Parse(offenderHtml, @"(?si:Race\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.eye_color = RegExpr.Parse(offenderHtml, @"(?si:Eyes\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.hair_color = RegExpr.Parse(offenderHtml, @"(?si:Hair\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.height = RegExpr.Parse(offenderHtml, @"(?si:Height\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.weight = RegExpr.Parse(offenderHtml, @"(?si:Weight\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.sex = RegExpr.Parse(offenderHtml, @"(?si:Sex\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.marks = RegExpr.Parse(offenderHtml, @"(?si:Scars/Tattoos\s*:\s*</strong>\s*</td>\s*<td>(.*?)</td>)").Trim();

                or.Address = HtmlUtils.KillSpace(RegExpr.Replace(
                    RegExpr.Parse(offenderHtml, @"(?si:Address\s*</td>\s*</tr>\s*<tr>\s*<td>(.*?)</td>)").Trim(),
                    @"(?si:<[\/]*?\s*br\s*[\/]*?>)", "@"));
                or.waddress = HtmlUtils.KillSpace(RegExpr.Replace(
                    RegExpr.Parse(offenderHtml, @"(?si:Work Addresses\s*</strong>\s*</td>\s*</tr>\s*<tr>\s*<td>\s*<blockquote>\s*<table>\s*<tr>\s*<td>(.*?)</td>)").Trim(),
                    @"(?si:<[\/]*?\s*br\s*[\/]*?>)", "@"));

                mc = RegExpr.ParseMatchCollection(offenderHtml, @"(?si:Description:\s*</strong>\s*</td>\s*<td>([^<>]*?)<)");
                if (mc.Count > 0) or.Crime += "Description; Date Convicted; Date Released; Details~";
                for (int i = 0; i < mc.Count; i++)
                {
                    or.Crime += String.Format("{0};", mc[i].Groups[1].Value.Trim());

                    try
                    {
                        or.Crime += String.Format("{0};", RegExpr.ParseMatchCollection(offenderHtml,
                            @"(?si:Date\s*Convicted:\s*</strong>\s*</td>\s*<td>(.*?)</td>)")[i].Groups[1].Value.Trim());
                    }
                    catch { or.Crime += ";"; }
                    try
                    {
                        or.Crime += String.Format("{0};", RegExpr.ParseMatchCollection(offenderHtml,
                            @"(?si:Date\s*Released:\s*</strong><\s*/td>\s*<td>(.*?)</td>)")[i].Groups[1].Value.Trim());
                    }
                    catch { or.Crime += ";"; }
                    try
                    {
                        or.Crime += String.Format("{0}~", RegExpr.ParseMatchCollection(offenderHtml,
                            @"(?si:Details:\s*</strong>\s*</td>\s*<td>(.*?)</td>)")[i].Groups[1].Value.Trim());
                    }
                    catch { or.Crime += "~"; }
                }

                ResultFile.Write(or.GetValues());
            }
        }

        private void UpdateProgress()
        {
            if (Params.GetIntegerParam(DmConsts.StartPosition) > Params.GetIntegerParam(DmConsts.EndPositon))
                throw new Exception(String.Format("Invalid parameters {2}[{0}]; {3} [{1}]",
                    Params.GetIntegerParam(DmConsts.StartPosition), Params.GetIntegerParam(DmConsts.EndPositon),
                    DmConsts.StartPosition, DmConsts.EndPositon));
            if (Params.GetIntegerParam(DmConsts.LastPosition) > Params.GetIntegerParam(DmConsts.EndPositon))
                throw new Exception(String.Format("Invalid parameters {2}[{0}]; {3} [{1}]",
                    Params.GetIntegerParam(DmConsts.LastPosition), Params.GetIntegerParam(DmConsts.EndPositon),
                    DmConsts.StartPosition, DmConsts.EndPositon));
            if (Params.GetIntegerParam(DmConsts.StartPosition) > Params.GetIntegerParam(DmConsts.LastPosition))
                throw new Exception(String.Format("Invalid parameters {2}[{0}]; {3} [{1}]",
                    Params.GetIntegerParam(DmConsts.StartPosition), Params.GetIntegerParam(DmConsts.LastPosition),
                    DmConsts.StartPosition, DmConsts.LastPosition));
            UpdateProgress(((double)(Params.GetIntegerParam(DmConsts.LastPosition) - Params.GetIntegerParam(DmConsts.StartPosition))) / (Params.GetIntegerParam(DmConsts.EndPositon) - Params.GetIntegerParam(DmConsts.StartPosition) + 1));
        }
    }
}
